/**
 * Copyright (C) 2014 Paul Cech
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * you may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package org.eazegraph.lib.charts;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ScrollHelper;
import ohos.agp.render.Arc;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;
import org.eazegraph.lib.ValueAnimator;
import org.eazegraph.lib.communication.IOnItemFocusChangedListener;
import org.eazegraph.lib.gesture.GestureDetector;
import org.eazegraph.lib.models.PieModel;
import org.eazegraph.lib.utils.AttrUtils;
import org.eazegraph.lib.utils.LogUtil;
import org.eazegraph.lib.utils.Utils;

import java.util.ArrayList;
import java.util.List;

/**
 * A customizable PieChart which can be rotated to select a Pie Slice. It is possible to show a normal Pie Chart or
 * a Doughnut Pie Chart depending on the InnerPadding attributes.
 */
public class PieChart extends BaseChart {

    /**
     * * The constant DEF_INNER_PADDING
     */
    public static final float DEF_INNER_PADDING = 65.f;

    /**
     * * The constant DEF_INNER_PADDING_OUTLINE
     */
    public static final float DEF_INNER_PADDING_OUTLINE = 5.f;

    /**
     * * The constant DEF_USE_INNER_PADDING
     */
    public static final boolean DEF_USE_INNER_PADDING = true;

    /**
     * * The constant DEF_HIGHLIGHT_STRENGTH
     */
    public static final float DEF_HIGHLIGHT_STRENGTH = 1.15f;

    /**
     * * The constant DEF_USE_PIE_ROTATION
     */
    public static final boolean DEF_USE_PIE_ROTATION = true;

    /**
     * * The constant DEF_AUTO_CENTER
     */
    public static final boolean DEF_AUTO_CENTER = true;

    /**
     * * The constant DEF_DRAW_VALUE_IN_PIE
     */
    public static final boolean DEF_DRAW_VALUE_IN_PIE = true;

    /**
     * * The constant DEF_VALUE_TEXT_SIZE
     */
    public static final float DEF_VALUE_TEXT_SIZE = 14.f;

    /**
     * * The constant DEF_VALUE_TEXT_COLOR
     */
    public static final int DEF_VALUE_TEXT_COLOR = 0xFF898989;

    /**
     * * The constant DEF_USE_CUSTOM_INNER_VALUE
     */
    public static final boolean DEF_USE_CUSTOM_INNER_VALUE = false;

    /**
     * * The constant DEF_OPEN_CLOCKWISE
     */
    public static final boolean DEF_OPEN_CLOCKWISE = true;

    /**
     * * The constant DEF_INNER_PADDING_COLOR
     */
    public static final int DEF_INNER_PADDING_COLOR = 0xFFF3F3F3; // Holo light background

    /**
     * * The constant DEF_INNER_VALUE_UNIT
     */
    public static final String DEF_INNER_VALUE_UNIT = "";

    /**
     * The initial fling velocity is divided by this amount.
     */
    public static final int FLING_VELOCITY_DOWNSCALE = 4;

    /**
     * * The constant AUTOCENTER_ANIM_DURATION
     */
    public static final int AUTOCENTER_ANIM_DURATION = 250;
    /**
     * * The constant TAG
     */
    private static final String TAG = "PieChart";


    /**
     * Constructor that is called when inflating a view from XML. This is called
     * when a view is being constructed from an XML file, supplying attributes
     * that were specified in the XML file. This version uses a default style of
     * 0, so the only attribute values applied are those in the Context's Theme
     * and the given AttributeSet.
     * <p/>
     * <p/>
     * The method onFinishInflate() will be called after all children have been
     * added.
     *
     * @param context The Context the view is running in, through which it can                access the current theme, resources, etc.
     * @param attrs The attributes of the XML tag that is inflating the view.
     * @see int)
     */
    private final String PieChart_egUseInnerPadding = "egUseInnerPadding";

    /**
     * * The constant PieChart_egInnerPadding
     */
    private final String PieChart_egInnerPadding = "egInnerPadding";

    /**
     * * The constant PieChart_egInnerPaddingOutline
     */
    private final String PieChart_egInnerPaddingOutline = "egInnerPaddingOutline";

    /**
     * * The constant PieChart_egHighlightStrength
     */
    private final String PieChart_egHighlightStrength = "egHighlightStrength";

    /**
     * * The constant PieChart_egUsePieRotation
     */
    private final String PieChart_egUsePieRotation = "egUsePieRotation";

    /**
     * * The constant PieChart_egAutoCenter
     */
    private final String PieChart_egAutoCenter = "egAutoCenter";

    /**
     * * The constant PieChart_egDrawValueInPie
     */
    private final String PieChart_egDrawValueInPie = "egDrawValueInPie";

    /**
     * * The constant PieChart_egValueTextSize
     */
    private final String PieChart_egValueTextSize = "egValueTextSize";

    /**
     * * The constant PieChart_egValueTextColor
     */
    private final String PieChart_egValueTextColor = "egValueTextColor";


    /**
     * * The constant PieChart_egUseCustomInnerValue
     */
    private final String PieChart_egUseCustomInnerValue = "egUseCustomInnerValue";

    /**
     * * The constant PieChart_egOpenClockwise
     */
    private final String PieChart_egOpenClockwise = "egOpenClockwise";

    /**
     * * The constant PieChart_egInnerPaddingColor
     */
    private final String PieChart_egInnerPaddingColor = "egInnerPaddingColor";

    /**
     * * The constant PieChart_egInnerValueUnit
     */
    private final String PieChart_egInnerValueUnit = "egInnerValueUnit";

    /**
     * * The constant LOG_TAG
     */
    private final String LOG_TAG = PieChart.class.getSimpleName();

    /**
     * The constant M pie data
     */
    private List<PieModel> mPieData;

    /**
     * The constant M graph paint
     */
    private Paint mGraphPaint;

    /**
     * The constant M legend paint
     */
    private Paint mLegendPaint;

    /**
     * The constant M value paint
     */
    private Paint mValuePaint;

    /**
     * The constant M graph bounds
     */
    private RectFloat mGraphBounds;

    /**
     * The constant M inner bounds
     */
    private RectFloat mInnerBounds;

    /**
     * The constant M inner outline bounds
     */
    private RectFloat mInnerOutlineBounds;

    /**
     * The constant M value text bounds Inner Value stuff
     */
    private Rect mValueTextBounds = new Rect();

    /**
     * The constant M indicator size Legend stuff
     */
    private float mIndicatorSize = Utils.dpToPx(getContext(),8);

    /**
     * The constant M indicator top margin
     */
    private float mIndicatorTopMargin = Utils.dpToPx(getContext(),6);

    /**
     * The constant M indicator bottom margin
     */
    private float mIndicatorBottomMargin = Utils.dpToPx(getContext(),4);

    /**
     * The constant M triangle
     */
    private Path mTriangle;

    /**
     * The constant M text bounds
     */
    private Rect mTextBounds = new Rect();

    /**
     * The constant M pie diameter
     */
    private float mPieDiameter;

    /**
     * The constant M pie radius
     */
    private float mPieRadius;

    /**
     * The constant M total value
     */
    private float mTotalValue;

    /**
     * The constant M inner value string
     */
    private String mInnerValueString = "";

    /**
     * The constant M use inner padding
     */
    private boolean mUseInnerPadding;

    /**
     * The constant M inner padding
     */
    private float mInnerPadding;

    /**
     * The constant M inner padding outline
     */
    private float mInnerPaddingOutline;

    /**
     * The constant M inner padding color
     */
    private int mInnerPaddingColor;

    /**
     * The constant M highlight strength
     */
    private float mHighlightStrength;

    /**
     * The constant M auto center in slice
     */
    private boolean mAutoCenterInSlice;

    /**
     * The constant M use pie rotation
     */
    private boolean mUsePieRotation;

    /**
     * The constant M draw value in pie
     */
    private boolean mDrawValueInPie;

    /**
     * The constant M value text size
     */
    private float mValueTextSize;

    /**
     * The constant M value text color
     */
    private int mValueTextColor;

    /**
     * The constant M use custom inner value
     */
    private boolean mUseCustomInnerValue;

    /**
     * The constant M open clockwise
     */
    private boolean mOpenClockwise;

    /**
     * The constant M inner value unit
     */
    private String mInnerValueUnit;

    /**
     * The constant M calculated inner padding
     */
    private float mCalculatedInnerPadding;

    /**
     * The constant M calculated inner padding outline
     */
    private float mCalculatedInnerPaddingOutline;

    /**
     * The constant M pie rotation
     */
    private int mPieRotation;

    /**
     * The constant M indicator angleIndicator is located at the bottom
     */
    private int mIndicatorAngle = 90;
    /**
     * The constant M current item
     */
    private int mCurrentItem = 0;
    /**
     * The constant M auto center animator
     */
    private ValueAnimator mAutoCenterAnimator;


    /**
     * The constant M scroller
     */
    private ScrollHelper mScroller;
    /**
     * The constant M scroll animator
     */
    private ValueAnimator mScrollAnimator;
    /**
     * The constant M detector
     */
    private GestureDetector mDetector;
    /**
     * The constant M listener
     */
    private IOnItemFocusChangedListener mListener;

    /**
     * Simple constructor to use when creating a view from code.
     *
     * @param context The Context the view is running in, through which it can                access the current theme, resources, etc.
     */
    public PieChart(Context context) {
        super(context);

        mUseInnerPadding = DEF_USE_INNER_PADDING;
        mInnerPadding = DEF_INNER_PADDING;
        mInnerPaddingOutline = DEF_INNER_PADDING_OUTLINE;
        mHighlightStrength = DEF_HIGHLIGHT_STRENGTH;
        mUsePieRotation = DEF_USE_PIE_ROTATION;
        mAutoCenterInSlice = DEF_AUTO_CENTER;
        mDrawValueInPie = DEF_DRAW_VALUE_IN_PIE;
        mValueTextSize = Utils.dpToPx(getContext(),DEF_VALUE_TEXT_SIZE);
        mValueTextColor = DEF_VALUE_TEXT_COLOR;
        mUseCustomInnerValue = DEF_USE_CUSTOM_INNER_VALUE;
        mOpenClockwise = DEF_OPEN_CLOCKWISE;
        mInnerPaddingColor = DEF_INNER_PADDING_COLOR;
        mInnerValueUnit = DEF_INNER_VALUE_UNIT;

        initializeGraph();
    }

    /**
     * Pie chart
     *
     * @param context context
     * @param attrs   attrs
     */
    public PieChart(Context context, AttrSet attrs) {
        super(context, attrs);


        mUseInnerPadding = AttrUtils.getBooleanFromAttr(attrs, PieChart_egUseInnerPadding, DEF_USE_INNER_PADDING);
        mInnerPadding = AttrUtils.getFloatFromAttr(attrs, PieChart_egInnerPadding, DEF_INNER_PADDING);
        mInnerPaddingOutline = AttrUtils.getFloatFromAttr(attrs, PieChart_egInnerPaddingOutline, DEF_INNER_PADDING_OUTLINE);
        mHighlightStrength = AttrUtils.getFloatFromAttr(attrs, PieChart_egHighlightStrength, DEF_HIGHLIGHT_STRENGTH);
        mUsePieRotation = AttrUtils.getBooleanFromAttr(attrs, PieChart_egUsePieRotation, DEF_USE_PIE_ROTATION);

        mAutoCenterInSlice = AttrUtils.getBooleanFromAttr(attrs, PieChart_egAutoCenter, DEF_AUTO_CENTER);
        mDrawValueInPie = AttrUtils.getBooleanFromAttr(attrs, PieChart_egDrawValueInPie, DEF_DRAW_VALUE_IN_PIE);
        mValueTextSize = AttrUtils.getDimensionFromAttr(attrs, PieChart_egValueTextSize, (int) Utils.dpToPx(getContext(),DEF_VALUE_TEXT_SIZE));
        mValueTextColor = AttrUtils.getColorFromAttr(attrs, PieChart_egValueTextColor, DEF_VALUE_TEXT_COLOR);
        mUseCustomInnerValue = AttrUtils.getBooleanFromAttr(attrs, PieChart_egUseCustomInnerValue, DEF_USE_CUSTOM_INNER_VALUE);

        mOpenClockwise = AttrUtils.getBooleanFromAttr(attrs, PieChart_egOpenClockwise, DEF_OPEN_CLOCKWISE);
        mInnerPaddingColor = AttrUtils.getColorFromAttr(attrs, PieChart_egInnerPaddingColor, DEF_INNER_PADDING_COLOR);
        mInnerValueUnit = AttrUtils.getStringFromAttr(attrs, PieChart_egInnerValueUnit, "");


        initializeGraph();
    }

    /**
     * Sets the onItemFocusChangedListener.
     *
     * @param _listener The instance of the IOnItemFocusChangedListener interface.
     */
    public void setOnItemFocusChangedListener(IOnItemFocusChangedListener _listener) {
        mListener = _listener;
    }

    /**
     * Checks if the InnerPadding is set or not. If it's set, a Doughnut Pie Chart is displayed.
     *
     * @return True if InnerPadding should be used.
     */
    public boolean isUseInnerPadding() {
        return mUseInnerPadding;
    }

    /**
     * Sets the InnerPadding. If the InnerPadding should be used, a complete recalculation is initiated.
     *
     * @param _useInnerPadding Indicates whether to use InnerPadding or not.
     */
    public void setUseInnerPadding(boolean _useInnerPadding) {
        mUseInnerPadding = _useInnerPadding;
        onDataChanged();
    }

    /**
     * Returns the InnerPadding's value.
     *
     * @return The InnerPadding's value in percent (between 0 - 100)
     */
    public float getInnerPadding() {
        return mInnerPadding;
    }

    /**
     * Sets the InnerPadding's value. After setting a recalculation is initiated.
     *
     * @param _innerPadding The InnerPadding's value in percent (between 0 - 100)
     */
    public void setInnerPadding(float _innerPadding) {
        mInnerPadding = _innerPadding;
        onDataChanged();
    }

    /**
     * Returns the color of the InnerPadding.
     *
     * @return the color of the InnerPadding
     */
    public int getInnerPaddingColor() {
        return mInnerPaddingColor;
    }

    /**
     * Sets the InnerPadding's color. After setting a recalculation is initiated.
     *
     * @param color the new InnerPadding's color
     */
    public void setInnerPaddingColor(int color) {
        mInnerPaddingColor = color;
        invalidateGraph();
    }

    /**
     * Returns the size of the InnerPaddingOutline (which is the highlighted part).
     *
     * @return The outline size in percent (between 0 - 100) dependent on the normal InnerPadding.
     */
    public float getInnerPaddingOutline() {
        return mInnerPaddingOutline;
    }

    /**
     * Sets the outline size of the InnerPadding.
     *
     * @param _innerPaddingOutline The outline size in percent (between 0 - 100) dependent on the normal InnerPadding.
     */
    public void setInnerPaddingOutline(float _innerPaddingOutline) {
        mInnerPaddingOutline = _innerPaddingOutline;
        onDataChanged();
    }

    /**
     * Returns the highlight strength for the InnerPaddingOutline.
     *
     * @return The highlighting value for the outline.
     */
    public float getHighlightStrength() {
        return mHighlightStrength;
    }

    /**
     * Sets the highlight strength for the InnerPaddingOutline.
     *
     * @param _highlightStrength The highlighting value for the outline.
     */
    public void setHighlightStrength(float _highlightStrength) {
        mHighlightStrength = _highlightStrength;
        for (PieModel model : mPieData) {
            highlightSlice(model);
        }
        invalidateGlobal();
    }

    /**
     * Checks if the AutoCenter is activated or not.
     * AutoCenter is only available for API Level 11 and higher.
     *
     * @return True if AutoCenter is activated.
     */
    public boolean isAutoCenterInSlice() {
        return mAutoCenterInSlice;
    }

    /**
     * Sets the AutoCenter property.
     * AutoCenter is only available for API Level 11 and higher.
     *
     * @param _autoCenterInSlice True when AutoCenter should be used.
     */
    public void setAutoCenterInSlice(boolean _autoCenterInSlice) {
        mAutoCenterInSlice = _autoCenterInSlice;
    }

    /**
     * Checks if the PieRotation is enabled or not.
     *
     * @return True if rotation is enabled.
     */
    public boolean isUsePieRotation() {
        return mUsePieRotation;
    }

    /**
     * Sets the PieRotation property to activate or deactivate the rotation.
     *
     * @param _usePieRotation True if rotation should be enabled.
     */
    public void setUsePieRotation(boolean _usePieRotation) {
        mUsePieRotation = _usePieRotation;
    }

    /**
     * Checks if the currently selected PieSlice's value should be drawn in the center.
     *
     * @return True if the value is drawn in the center.
     */
    public boolean isDrawValueInPie() {
        return mDrawValueInPie;
    }

    /**
     * Sets the property which indicates whether the currently selected PieSlice's value should be
     * drawn in the center or not.
     *
     * @param _drawValueInPie True if the value should be drawn in the center.
     */
    public void setDrawValueInPie(boolean _drawValueInPie) {
        mDrawValueInPie = _drawValueInPie;
        invalidateGlobal();
    }

    /**
     * Returns the text size of the value which is drawn in the center of the PieChart.
     *
     * @return The value's text size.
     */
    public float getValueTextSize() {
        return mValueTextSize;
    }

    /**
     * Returns the color of the text which is drawn in the center of the PieChart.
     *
     * @return Color value of the text.
     */
    public int getValueTextColor() {
        return mValueTextColor;
    }

    /**
     * Returns the custom String which is displayed in the center of the PieChart.
     *
     * @return Value of String.
     */
    public String getInnerValueString() {
        return mInnerValueString;
    }

    /**
     * Sets the custom String which is displayed in the center of the PieChart.
     *
     * @param _innerValueString Custom String.
     */
    public void setInnerValueString(String _innerValueString) {
        mInnerValueString = _innerValueString;
        invalidateGraphOverlay();
    }

    /**
     * Checks if a custom inner value should be displayed or not.
     *
     * @return True if a custom inner value is shown.
     */
    public boolean isUseCustomInnerValue() {
        return mUseCustomInnerValue;
    }

    /**
     * Sets the indication whether a custom inner value should be drawn or not. If it's true, no
     * value of the currently selected PieSlcie is shown in the center of the PieChart.
     *
     * @param _useCustomInnerValue True if a custom inner value should be used.
     */
    public void setUseCustomInnerValue(boolean _useCustomInnerValue) {
        mUseCustomInnerValue = _useCustomInnerValue;
    }

    /**
     * Checks if the animation should open clockwise or counter-clockwise.
     *
     * @return True for clockwise.
     */
    public boolean isOpenClockwise() {
        return mOpenClockwise;
    }

    /**
     * Sets if the starting animation should be opened clockwise or counter-clockwise.
     *
     * @param _openClockwise True for a clockwise aniamtion.
     */
    public void setOpenClockwise(boolean _openClockwise) {
        mOpenClockwise = _openClockwise;
    }

    /**
     * Get inner value unit string
     *
     * @return The unit which is displayed after the value in the center of the PieChart.
     */
    public String getInnerValueUnit() {
        return mInnerValueUnit;
    }

    /**
     * Sets the unit which will be displayed after the value in the center of the PieChart.
     *
     * @param _innerValueUnit The unit appendix
     */
    public void setInnerValueUnit(String _innerValueUnit) {
        mInnerValueUnit = _innerValueUnit;
    }

    /**
     * Returns the index of the currently selected data item.
     *
     * @return The zero-based index of the currently selected data item.
     */
    public int getCurrentItem() {
        return mCurrentItem;
    }

    /**
     * Set the currently selected item. Calling this function will set the current selection
     * and rotate the pie to bring it into view.
     *
     * @param currentItem The zero-based index of the item to select.
     */
    public void setCurrentItem(int currentItem) {
        setCurrentItem(currentItem, true);
    }

    /**
     * Set the current item by index. Optionally, scroll the current item into view. This version
     * is for internal use--the scrollIntoView option is always true for external callers.
     *
     * @param currentItem    The index of the current item.
     * @param scrollIntoView True if the pie should rotate until the current item is centered.                       False otherwise. If this parameter is false, the pie rotation                       will not change.
     */
    private void setCurrentItem(int currentItem, boolean scrollIntoView) {
        mCurrentItem = currentItem;
        if (mListener != null) {
            mListener.onItemFocusChanged(currentItem);
        }
        if (scrollIntoView) {
            centerOnCurrentItem();
        }
        invalidateGlobal();
    }

    /**
     * Returns the current rotation of the pie graphic.
     *
     * @return The current pie rotation, in degrees.
     */
    public int getPieRotation() {
        return mPieRotation;
    }

    /**
     * Set the current rotation of the pie graphic. Setting this value may change
     * the current item.
     *
     * @param rotation The current pie rotation, in degrees.
     */
    public void setPieRotation(int rotation) {
        mPieRotation = (rotation % 360 + 360) % 360;
        LogUtil.info(TAG, "setPieRotation: " + mPieRotation);
        mGraph.rotateTo(mPieRotation);

        calcCurrentItem();
    }

    /**
     * Adds a new Pie Slice to the PieChart. After inserting and calculation of the highlighting color
     * a complete recalculation is initiated.
     *
     * @param _Slice The newly added PieSlice.
     */
    public void addPieSlice(PieModel _Slice) {
        if (_Slice == null) {
            return;
        }        
        highlightSlice(_Slice);
        mPieData.add(_Slice);
        mTotalValue += _Slice.getValue();
        onDataChanged();
    }

    /**
     * Resets and clears the data object.
     */
    @Override
    public void update() {
        mTotalValue = 0;
        for (PieModel slice : mPieData) {
            mTotalValue += slice.getValue();
        }
        onDataChanged();
    }

    /**
     * Resets and clears the data object.
     */
    @Override
    public void clearChart() {
        mPieData.clear();
        mTotalValue = 0;
    }

    /**
     * On touch event boolean
     *
     * @param component  component
     * @param touchEvent touch event
     * @return the boolean
     */
    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        super.onTouchEvent(component, touchEvent);
        boolean result = false;

        if (mUsePieRotation) {
            result = mDetector.onTouchEvent(touchEvent);

            LogUtil.info(TAG, "onTouchEvent: result: " + result + " touchEvent: " + touchEvent.getAction());
            // If the GestureDetector doesn't want this event, do some custom processing.
            // This code just tries to detect when the user is done scrolling by looking
            // for ACTION_UP events.
            if (!result) {
                if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {
                    // User is done scrolling, it's now safe to do things like autocenter
                    stopScrolling();
                    result = true;
                }
            }
        } else if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
            simulateClick();
            result = true;
        }

        return result;
    }

    /**
     * On draw *
     *
     * @param component component
     * @param canvas    canvas
     */
    @Override
    public void onDraw(Component component, Canvas canvas) {
        super.onDraw(component, canvas);
    }

    /**
     * This is called during layout when the size of this view has changed. If
     * you were just added to the view hierarchy, you're called with the old
     * values of 0.
     *
     * @param component component
     */
    @Override
    public void onRefreshed(Component component) {
        super.onRefreshed(component);
        mGraph.setPivot(mGraphBounds.getCenter().getPointX(), mGraphBounds.getCenter().getPointY());

        onDataChanged();
    }

    /**
     * This is the main entry point after the graph has been inflated. Used to initialize the graph
     * and its corresponding members.
     */
    @Override
    protected void initializeGraph() {
        super.initializeGraph();
        mPieData = new ArrayList<PieModel>();
        mTotalValue = 0;
        mGraphPaint = new Paint();
        mGraphPaint.setAntiAlias(true);
        mLegendPaint = new Paint();
        mLegendPaint.setAntiAlias(true);
        mLegendPaint.setTextSize((int) mLegendTextSize);
        mLegendPaint.setColor(new Color(mLegendColor));
        mLegendPaint.setStyle(Paint.Style.FILL_STYLE);

        mValuePaint = new Paint();
        mValuePaint.setAntiAlias(true);
        mValuePaint.setTextSize((int) mValueTextSize);
        mValuePaint.setColor(new Color(mValueTextColor));
        mValuePaint.setStyle(Paint.Style.FILL_STYLE);

        mGraph.rotateTo(mPieRotation);
        mGraph.decelerate();

        mRevealAnimator = ValueAnimator.ofFloat(0, 1);
        mRevealAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                mRevealValue = value;
                invalidateGlobal();
            }
        });
        mRevealAnimator.setStateChangedListener(stateChangedListener);

        if (mUsePieRotation) {
            // Set up an animator to animate the PieRotation property. This is used to
            // correct the pie's orientation after the user lets go of it.
            mAutoCenterAnimator = new ValueAnimator();
            mAutoCenterAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float value) {
                    setPieRotation((int) (value));
                }
            });

            // Add a listener to hook the onAnimationEnd event so that we can do
            // some cleanup when the pie stops moving.

            // Create a Scroller to handle the fling gesture.

            mScroller = new ScrollHelper();


            // The scroller doesn't have any built-in animation functions--it just supplies
            // values when we ask it to. So we have to have a way to call it every frame
            // until the fling ends. This code (ab)uses a ValueAnimator object to generate
            // a callback on every animation frame. We don't use the animated value at all.
            mScrollAnimator = ValueAnimator.ofFloat(0, 1);
            mScrollAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float value) {
                    tickScrollAnimation();
                }
            });

            mScrollAnimator.setStateChangedListener(mScrollAnimatorStateChangedListener);
            // Create a gesture detector to handle onTouch messages
            mDetector = new GestureDetector(PieChart.this.getContext(), new GestureListener());

            // Turn off long press--this control doesn't use it, and if long press is enabled,
            // you can't scroll for a bit, pause, then scroll some more (the pause is interpretedas a long press, apparently)
            mDetector.setIsLongpressEnabled(false);
        }
    }

    private Animator.StateChangedListener mScrollAnimatorStateChangedListener = new Animator.StateChangedListener() {
        @Override
        public void onStart(Animator animator) {

        }

        @Override
        public void onStop(Animator animator) {

        }

        @Override
        public void onCancel(Animator animator) {

        }

        @Override
        public void onEnd(Animator animator) {
            centerOnCurrentItem();
        }

        @Override
        public void onPause(Animator animator) {

        }

        @Override
        public void onResume(Animator animator) {

        }
    };
    private Animator.StateChangedListener stateChangedListener = new Animator.StateChangedListener() {
        @Override
        public void onStart(Animator animator) {

        }

        @Override
        public void onStop(Animator animator) {

        }

        @Override
        public void onCancel(Animator animator) {

        }

        @Override
        public void onEnd(Animator animator) {
            mStartedAnimation = false;
        }

        @Override
        public void onPause(Animator animator) {

        }

        @Override
        public void onResume(Animator animator) {

        }
    };

    /**
     * Should be called after new data is inserted. Will be automatically called, when the view dimensions
     * has changed.
     * <p>
     * Calculates the start- and end-angles for every PieSlice.
     */
    @Override
    protected void onDataChanged() {
        super.onDataChanged();

        int currentAngle = 0;
        int index = 0;
        int size = mPieData.size();

        for (PieModel model : mPieData) {
            int endAngle = (int) (currentAngle + model.getValue() * 360.f / mTotalValue);
            if (index == size - 1) {
                endAngle = 360;
            }

            model.setStartAngle(currentAngle);
            model.setEndAngle(endAngle);
            currentAngle = model.getEndAngle();
            index++;
        }
        calcCurrentItem();
        onScrollFinished();
    }

    /**
     * Calculate the highlight color. Saturate at 0xff to make sure that high values
     * don't result in aliasing.
     *
     * @param _Slice The Slice which will be highlighted.
     */
    private void highlightSlice(PieModel _Slice) {

        int color = _Slice.getColor();
        _Slice.setHighlightedColor(Color.argb(
                0xff,
                Math.min((int) (mHighlightStrength * (float) RgbColor.fromArgbInt(color).getRed()), 0xff),
                Math.min((int) (mHighlightStrength * (float) RgbColor.fromArgbInt(color).getGreen()), 0xff),
                Math.min((int) (mHighlightStrength * (float) RgbColor.fromArgbInt(color).getBlue()), 0xff)
        ));
    }

    /**
     * Calculate which pie slice is under the pointer, and set the current item
     * field accordingly.
     */
    private void calcCurrentItem() {
        int pointerAngle;

        // calculate the correct pointer angle, depending on clockwise drawing or not
        if (mOpenClockwise) {
            pointerAngle = (mIndicatorAngle + 360 - mPieRotation) % 360;
        } else {
            pointerAngle = (mIndicatorAngle + 180 + mPieRotation) % 360;
        }

        for (int i = 0; i < mPieData.size(); ++i) {
            PieModel model = mPieData.get(i);
            if (model.getStartAngle() <= pointerAngle && pointerAngle <= model.getEndAngle()) {
                if (i != mCurrentItem) {
                    setCurrentItem(i, false);
                }
                break;
            }
        }
    }

    /**
     * Tick scroll animation
     */
    private void tickScrollAnimation() {
        if (!mScroller.isFinished()) {
            mScroller.updateScroll();
            setPieRotation(mScroller.getCurrValue(ScrollHelper.AXIS_Y));
        } else {
            mScroller.abortAnimation();
            onScrollFinished();
        }
    }

    /**
     * Force a stop to all pie motion. Called when the user taps during a fling.
     */
    private void stopScrolling() {
        mAutoCenterAnimator.cancel();

        mScroller.abortAnimation();
        onScrollFinished();
    }

    /**
     * Called when the user finishes a scroll action.
     */
    private void onScrollFinished() {
        if (mAutoCenterInSlice) {
            centerOnCurrentItem();
        } else {
            mGraph.decelerate();
        }
    }

    /**
     * Kicks off an animation that will result in the pointer being centered in the
     * pie slice of the currently selected item.
     */
    private void centerOnCurrentItem() {
        if (!mPieData.isEmpty()) {
            PieModel current = mPieData.get(getCurrentItem());
            int targetAngle;

            if (mOpenClockwise) {
                targetAngle = (mIndicatorAngle - current.getStartAngle()) - ((current.getEndAngle() - current.getStartAngle()) / 2);
                if (targetAngle < 0 && mPieRotation > 0) {
                    targetAngle += 360;
                }
            } else {
                targetAngle = current.getStartAngle() + (current.getEndAngle() - current.getStartAngle()) / 2;
                targetAngle += mIndicatorAngle;
                if (targetAngle > 270 && mPieRotation < 90) {
                    targetAngle -= 360;
                }
            }

            mAutoCenterAnimator.setFloatValues(mPieRotation, targetAngle);
            mAutoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);
            mAutoCenterAnimator.start();

        }
    }

    /**
     * Returns the graph boundaries.
     *
     * @return Graph bounds.
     */
    private RectFloat getGraphBounds() {
        return mGraphBounds;
    }

    /**
     * Returns the datasets which are currently inserted.
     *
     * @return the datasets
     */
    @Override
    public List<PieModel> getData() {
        return mPieData;
    }

    /**
     * On graph draw *
     *
     * @param _Canvas canvas
     */
    @Override
    protected void onGraphDraw(Canvas _Canvas) {
        super.onGraphDraw(_Canvas);

        if (!mPieData.isEmpty() && mGraphBounds != null) {
            float innerStartAngle = 0;
            float innerSweepAngle = 0;
            int amountOfPieSlices = mPieData.size();

            for (int pieIndex = 0; pieIndex < amountOfPieSlices; pieIndex++) {
                PieModel model = mPieData.get(pieIndex);

                mGraphPaint.setColor(new Color(model.getColor()));

                // TODO: put calculation in the animation onUpdate method and provide an animated value
                float startAngle;
                float sweepAngle = (model.getEndAngle() - model.getStartAngle()) * mRevealValue;

                if (mOpenClockwise) {
                    startAngle = model.getStartAngle() * mRevealValue;
                } else {
                    startAngle = 360 - model.getEndAngle() * mRevealValue;
                }

                if (pieIndex == 0) {
                    innerStartAngle = startAngle + (mOpenClockwise ? 0 : (float) Math.ceil(sweepAngle));
                }

                if (mOpenClockwise) {
                    innerSweepAngle += sweepAngle;
                } else {
                    innerSweepAngle -= (float) Math.ceil(sweepAngle);
                }

                _Canvas.drawArc(mGraphBounds,
                        new Arc(startAngle,
                                sweepAngle,
                                true), mGraphPaint);

                // Draw the highlighted inner edges if an InnerPadding is selected
                if (mUseInnerPadding) {
                    mGraphPaint.setColor(new Color(model.getHighlightedColor()));

                    _Canvas.drawArc(mInnerBounds,
                            new Arc(startAngle,
                                    sweepAngle,
                                    true), mGraphPaint);
                }
            }

            // Draw inner white circle
            if (mUseInnerPadding) {
                mGraphPaint.setColor(new Color(mInnerPaddingColor));

                _Canvas.drawArc(mInnerOutlineBounds,
                        new Arc(innerStartAngle,
                                innerSweepAngle,
                                true), mGraphPaint);
            }
        } else {
            drawArc(_Canvas);
        }
    }

    private void drawArc(Canvas _Canvas) {
        if (mGraphBounds == null) {
            return;
        }
        // No Data available
        mGraphPaint.setColor(new Color(0xFFB6B6B6));
        _Canvas.drawArc(mGraphBounds,
                new Arc(0,
                        360,
                        true), mGraphPaint);

        // Draw inner white circle
        if (mUseInnerPadding) {
            mGraphPaint.setColor(new Color(0xFFC6C6C6));

            _Canvas.drawArc(mInnerBounds,
                    new Arc(0,
                            360,
                            true), mGraphPaint);

            mGraphPaint.setColor(new Color(mInnerPaddingColor));

            _Canvas.drawArc(mInnerOutlineBounds,
                    new Arc(0,
                            360,
                            true), mGraphPaint);
        }
    }

    /**
     * On graph overlay draw *
     *
     * @param _Canvas canvas
     */
    @Override
    protected void onGraphOverlayDraw(Canvas _Canvas) {
        super.onGraphOverlayDraw(_Canvas);

        if (!mPieData.isEmpty() && mDrawValueInPie && mInnerBounds != null) {
            PieModel model = mPieData.get(mCurrentItem);

            if (!mUseCustomInnerValue) {
                mInnerValueString = Utils.getFloatString(model.getValue(), mShowDecimal);
                if (mInnerValueUnit != null && mInnerValueUnit.length() > 0) {
                    mInnerValueString += " " + mInnerValueUnit;
                }
            }

            mValueTextBounds = mValuePaint.getTextBounds(mInnerValueString);
            _Canvas.drawText(mValuePaint,
                    mInnerValueString,
                    mInnerBounds.getCenter().getPointX() - (mValueTextBounds.getWidth() / 2),
                    mInnerBounds.getCenter().getPointY() + (mValueTextBounds.getHeight() / 2)

            );
        }
    }

    /**
     * On legend draw *
     *
     * @param _Canvas canvas
     */
    @Override
    protected void onLegendDraw(Canvas _Canvas) {
        super.onLegendDraw(_Canvas);

        _Canvas.drawPath(mTriangle, mLegendPaint);

        float height = mMaxFontHeight = Utils.calculateMaxTextHeight(mLegendPaint, null);

        if (!mPieData.isEmpty()) {
            PieModel model = mPieData.get(mCurrentItem);

            // center text in view
            // TODO: move the boundary calculation out of onDraw
            mTextBounds = mLegendPaint.getTextBounds(model.getLegendLabel());
            _Canvas.drawText(mLegendPaint,
                    model.getLegendLabel(),
                    (mLegendWidth / 2) - (mTextBounds.getWidth() / 2),
                    mIndicatorSize * 2 + mIndicatorBottomMargin + mIndicatorTopMargin + height

            );
        } else {

            mTextBounds = mLegendPaint.getTextBounds(mEmptyDataText);
            _Canvas.drawText(mLegendPaint,
                    mEmptyDataText,
                    (mLegendWidth / 2) - (mTextBounds.getWidth() / 2),
                    mIndicatorSize * 2 + mIndicatorBottomMargin + mIndicatorTopMargin + height

            );
        }
    }

    /**
     * On graph size changed *
     *
     * @param width    w
     * @param height    h
     * @param oldw oldw
     * @param oldh oldh
     */
    @Override
    protected void onGraphSizeChanged(int width, int height, int oldw, int oldh) {
        super.onGraphSizeChanged(width, height, oldw, oldh);

        // Figure out how big we can make the pie.
        mPieDiameter = Math.min(width, height);
        mPieRadius = mPieDiameter / 2.f;
        // calculate the left and right space to be center aligned
        float centeredValueWidth = (width - mPieDiameter) / 2f;
        float centeredValueHeight = (height - mPieDiameter) / 2f;
        mGraphBounds = new RectFloat(
                0.0f,
                0.0f,
                mPieDiameter,
                mPieDiameter);
        mGraphBounds.translateTo(centeredValueWidth, centeredValueHeight);
        LogUtil.info(TAG, "w: " + width + " h: " + height + "centeredValueWidth: " + centeredValueWidth + " centeredValueHeight: " + centeredValueHeight);

        mCalculatedInnerPadding = (mPieRadius / 100) * mInnerPadding;
        mCalculatedInnerPaddingOutline = (mPieRadius / 100) * mInnerPaddingOutline;

        mInnerBounds = new RectFloat(
                mGraphBounds.getCenter().getPointX() - mCalculatedInnerPadding - mCalculatedInnerPaddingOutline,
                mGraphBounds.getCenter().getPointY() - mCalculatedInnerPadding - mCalculatedInnerPaddingOutline,
                mGraphBounds.getCenter().getPointX() + mCalculatedInnerPadding + mCalculatedInnerPaddingOutline,
                mGraphBounds.getCenter().getPointY() + mCalculatedInnerPadding + mCalculatedInnerPaddingOutline);

        mInnerOutlineBounds = new RectFloat(
                mGraphBounds.getCenter().getPointX() - mCalculatedInnerPadding,
                mGraphBounds.getCenter().getPointY() - mCalculatedInnerPadding,
                mGraphBounds.getCenter().getPointX() + mCalculatedInnerPadding,
                mGraphBounds.getCenter().getPointY() + mCalculatedInnerPadding);
    }

    /**
     * On legend size changed *
     *
     * @param width    w
     * @param height    h
     * @param oldw oldw
     * @param oldh oldh
     */
    @Override
    protected void onLegendSizeChanged(int width, int height, int oldw, int oldh) {
        super.onLegendSizeChanged(width, height, oldw, oldh);

        mTriangle = new Path();
        mTriangle.moveTo((width / 2) - mIndicatorSize, mIndicatorSize * 2 + mIndicatorTopMargin);
        mTriangle.lineTo((width / 2) + mIndicatorSize, mIndicatorSize * 2 + mIndicatorTopMargin);
        mTriangle.lineTo(width / 2, mIndicatorTopMargin);
        mTriangle.lineTo((width / 2) - mIndicatorSize, mIndicatorSize * 2 + mIndicatorTopMargin);

    }

    /**
     * Checks if the animation is currently running.
     *
     * @return True if animation is running.
     */
    private boolean isAnimationRunning() {
        return !mScroller.isFinished() || mAutoCenterAnimator.isRunning();
    }

    /**
     * Extends {@link} to provide custom gesture
     * processing.
     */
    private class GestureListener extends GestureDetector.SimpleOnGestureListener {
        /**
         * On scroll boolean
         *
         * @param e1        e 1
         * @param e2        e 2
         * @param distanceX distance x
         * @param distanceY distance y
         * @return the boolean
         */
        @Override
        public boolean onScroll(TouchEvent e1, TouchEvent e2, float distanceX, float distanceY) {
            // Set the pie rotation directly.
            float scrollTheta = Utils.vectorToScalarScroll(
                    distanceX,
                    distanceY,
                    e2.getPointerPosition(0).getX() - getGraphBounds().getCenter().getPointX(),
                    e2.getPointerPosition(0).getY() - getGraphBounds().getCenter().getPointY());
            setPieRotation(mPieRotation - (int) scrollTheta / FLING_VELOCITY_DOWNSCALE);
            return true;
        }

        /**
         * On fling boolean
         *
         * @param e1        e 1
         * @param e2        e 2
         * @param velocityX velocity x
         * @param velocityY velocity y
         * @return the boolean
         */
        @Override
        public boolean onFling(TouchEvent e1, TouchEvent e2, float velocityX, float velocityY) {
            // Set up the Scroller for a fling
            float scrollTheta = Utils.vectorToScalarScroll(
                    velocityX,
                    velocityY,
                    e2.getPointerScreenPosition(0).getX() - getGraphBounds().getCenter().getPointX(),
                    e2.getPointerScreenPosition(0).getY() - getGraphBounds().getCenter().getPointY());

            mScroller.doFling(
                    0,
                    mPieRotation,
                    0,
                    (int) scrollTheta / 2,
                    0,
                    0,
                    Integer.MIN_VALUE,
                    Integer.MAX_VALUE);

            // Start the animator and tell it to animate for the expected duration of the fling.
            int splineFlingDuration = Utils.getSplineFlingDuration(Math.abs(velocityY), getContext());

            mScrollAnimator.setDuration(splineFlingDuration);
            mScrollAnimator.start();
            LogUtil.info(TAG, "onFling: " + splineFlingDuration + " scrollTheta: " + scrollTheta);
            return true;
        }

        /**
         * On down boolean
         *
         * @param event event
         * @return the boolean
         */
        @Override
        public boolean onDown(TouchEvent event) {
            // The user is interacting with the pie, so we want to turn on acceleration
            // so that the interaction is smooth.
            if (isAnimationRunning()) {
                stopScrolling();
            }
            return true;
        }

        /**
         * On single tap confirmed boolean
         *
         * @param event event
         * @return the boolean
         */
        @Override
        public boolean onSingleTapConfirmed(TouchEvent event) {
            simulateClick();
            return true;
        }
    }

}
